{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# !wget https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-12_H-768_A-12.zip\n",
    "# !unzip uncased_L-12_H-768_A-12.zip"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pickle\n",
    "import json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open('data.json') as fopen:\n",
    "    output = json.load(fopen)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 303625/303625 [00:55<00:00, 5462.11it/s]\n"
     ]
    }
   ],
   "source": [
    "import bert\n",
    "from bert import run_classifier\n",
    "from bert import optimization\n",
    "from bert import tokenization\n",
    "from bert import modeling\n",
    "from tqdm import tqdm\n",
    "\n",
    "BERT_VOCAB = 'uncased_L-12_H-768_A-12/vocab.txt'\n",
    "BERT_INIT_CHKPNT = 'uncased_L-12_H-768_A-12/bert_model.ckpt'\n",
    "BERT_CONFIG = 'uncased_L-12_H-768_A-12/bert_config.json'\n",
    "\n",
    "tokenization.validate_case_matches_checkpoint(True,BERT_INIT_CHKPNT)\n",
    "tokenizer = tokenization.FullTokenizer(\n",
    "      vocab_file=BERT_VOCAB, do_lower_case=True)\n",
    "\n",
    "MAX_SEQ_LENGTH = 100\n",
    "\n",
    "for i in tqdm(range(len(output))):\n",
    "    tokens_a = tokenizer.tokenize(output[i][0])\n",
    "    if len(tokens_a) > MAX_SEQ_LENGTH - 1:\n",
    "        tokens_a = tokens_a[:(MAX_SEQ_LENGTH - 1)]\n",
    "    tokens_a.append('|')\n",
    "    output[i][0] = tokens_a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "phr2sg_id = pickle.load(open('phr2sg_id.pkl', 'rb'))\n",
    "sg_id2phr = pickle.load(open('sg_id2phr.pkl', 'rb'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import Counter\n",
    "\n",
    "n_classes = 100\n",
    "\n",
    "sg_ids = []\n",
    "for line in output:\n",
    "    sg_id = line[1]\n",
    "    if sg_id != 0:\n",
    "        sg_ids.append(sg_id)\n",
    "sg_id2cnt = Counter(sg_ids)\n",
    "sg_ids = [sg_id for sg_id, cnt in sg_id2cnt.most_common(n_classes)]\n",
    "idx2sg_id = {idx: sg_id for idx, sg_id in enumerate(sg_ids)}\n",
    "sg_id2idx = {sg_id: idx for idx, sg_id in enumerate(sg_ids)}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "phr2idx = dict()\n",
    "for phr, sg_id in phr2sg_id.items():\n",
    "    if sg_id in sg_id2idx:\n",
    "        phr2idx[phr] = sg_id2idx[sg_id]\n",
    "        \n",
    "idx2phr = dict()\n",
    "for idx, sg_id in idx2sg_id.items():\n",
    "    if sg_id in sg_id2phr:\n",
    "        idx2phr[idx] = sg_id2phr[sg_id]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 303625/303625 [00:54<00:00, 5527.43it/s] \n"
     ]
    }
   ],
   "source": [
    "contexts_li = [[] for _ in range(n_classes)]\n",
    "for i, cols in tqdm(enumerate(output), total=len(output)):\n",
    "    if i==0: continue\n",
    "    sent, sg_id = cols\n",
    "    if sg_id in sg_id2idx:\n",
    "        idx = sg_id2idx[sg_id]\n",
    "        ctx = []\n",
    "        for l in output[:i][-10:]:\n",
    "            ctx.append(l[0])\n",
    "        contexts = contexts_li[idx]\n",
    "        contexts.append(ctx)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "train, dev = [], []\n",
    "for contexts in contexts_li:\n",
    "    if len(contexts) > 1:\n",
    "        train.append(contexts[1:])\n",
    "        dev.append(contexts[:1])\n",
    "    else:\n",
    "        train.append(contexts)\n",
    "        dev.append([])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using TensorFlow backend.\n"
     ]
    }
   ],
   "source": [
    "from itertools import chain\n",
    "from keras.preprocessing.sequence import pad_sequences\n",
    "\n",
    "def to_xy(data, max_span = 128):\n",
    "    x, y = [], []\n",
    "    for i in tqdm(range(n_classes)):\n",
    "        contexts = data[i]\n",
    "        for c in contexts:\n",
    "            flatted = list(chain.from_iterable(c))\n",
    "            for k in range(0, len(flatted), max_span):\n",
    "                index = min(k + max_span, len(flatted))\n",
    "                batch_x = flatted[k: index]\n",
    "                batch_x = [\"[CLS]\"] + batch_x + [\"[SEP]\"]\n",
    "                batch_x = tokenizer.convert_tokens_to_ids(batch_x)\n",
    "                x.append(batch_x)\n",
    "                y.append(i)\n",
    "    return pad_sequences(x, padding='post'), y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 100/100 [00:01<00:00, 73.32it/s]\n"
     ]
    }
   ],
   "source": [
    "train_X, train_Y = to_xy(train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 100/100 [00:00<00:00, 25063.07it/s]\n"
     ]
    }
   ],
   "source": [
    "test_X, test_Y = to_xy(dev)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "((63022, 130), (165, 130))"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_X.shape, test_X.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "epoch = 5\n",
    "batch_size = 50\n",
    "warmup_proportion = 0.1\n",
    "num_train_steps = int(train_X.shape[0] / batch_size * epoch)\n",
    "num_warmup_steps = int(num_train_steps * warmup_proportion)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Model:\n",
    "    def __init__(\n",
    "        self,\n",
    "        dimension_output,\n",
    "        learning_rate = 2e-5,\n",
    "    ):\n",
    "        self.X = tf.placeholder(tf.int32, [None, None])\n",
    "        self.Y = tf.placeholder(tf.int32, [None])\n",
    "        \n",
    "        model = modeling.BertModel(\n",
    "            config=bert_config,\n",
    "            is_training=True,\n",
    "            input_ids=self.X,\n",
    "            use_one_hot_embeddings=False)\n",
    "        \n",
    "        output_layer = model.get_pooled_output()\n",
    "        self.logits = tf.layers.dense(output_layer, dimension_output)\n",
    "        \n",
    "        self.cost = tf.reduce_mean(\n",
    "            tf.nn.sparse_softmax_cross_entropy_with_logits(\n",
    "                logits = self.logits, labels = self.Y\n",
    "            )\n",
    "        )\n",
    "        \n",
    "        self.optimizer = optimization.create_optimizer(self.cost, learning_rate, \n",
    "                                                       num_train_steps, num_warmup_steps, False)\n",
    "        correct_pred = tf.equal(\n",
    "            tf.argmax(self.logits, 1, output_type = tf.int32), self.Y\n",
    "        )\n",
    "        self.accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "import time\n",
    "from sklearn.utils import shuffle\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_X, train_Y = shuffle(train_X, train_Y)\n",
    "test_X, test_Y = shuffle(test_X, test_Y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "bert_config = modeling.BertConfig.from_json_file(BERT_CONFIG)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Colocations handled automatically by placer.\n",
      "\n",
      "WARNING: The TensorFlow contrib module will not be included in TensorFlow 2.0.\n",
      "For more information, please see:\n",
      "  * https://github.com/tensorflow/community/blob/master/rfcs/20180907-contrib-sunset.md\n",
      "  * https://github.com/tensorflow/addons\n",
      "If you depend on functionality not listed there, please file an issue.\n",
      "\n",
      "WARNING:tensorflow:From /home/jupyter/.local/lib/python3.6/site-packages/bert/modeling.py:358: calling dropout (from tensorflow.python.ops.nn_ops) with keep_prob is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please use `rate` instead of `keep_prob`. Rate should be set to `rate = 1 - keep_prob`.\n",
      "WARNING:tensorflow:From /home/jupyter/.local/lib/python3.6/site-packages/bert/modeling.py:671: dense (from tensorflow.python.layers.core) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use keras.layers.dense instead.\n",
      "WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/training/learning_rate_decay_v2.py:321: div (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Deprecated in favor of operator or tf.math.divide.\n",
      "WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/ops/math_ops.py:3066: to_int32 (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use tf.cast instead.\n",
      "WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/training/saver.py:1266: checkpoint_exists (from tensorflow.python.training.checkpoint_management) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use standard file APIs to check for files with this prefix.\n",
      "INFO:tensorflow:Restoring parameters from uncased_L-12_H-768_A-12/bert_model.ckpt\n"
     ]
    }
   ],
   "source": [
    "learning_rate = 2e-5\n",
    "\n",
    "tf.reset_default_graph()\n",
    "sess = tf.InteractiveSession()\n",
    "model = Model(\n",
    "    n_classes,\n",
    "    learning_rate\n",
    ")\n",
    "\n",
    "sess.run(tf.global_variables_initializer())\n",
    "var_lists = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope = 'bert')\n",
    "saver = tf.train.Saver(var_list = var_lists)\n",
    "saver.restore(sess, BERT_INIT_CHKPNT)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 1261/1261 [09:27<00:00,  2.59it/s, accuracy=0.136, cost=4.09]\n",
      "test minibatch loop: 100%|██████████| 4/4 [00:00<00:00,  4.72it/s, accuracy=0, cost=4.82]   \n",
      "train minibatch loop:   0%|          | 0/1261 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 0, pass acc: 0.000000, current acc: 0.012121\n",
      "time taken: 568.682032585144\n",
      "epoch: 0, training loss: 3.864423, training acc: 0.145216, valid loss: 6.014607, valid acc: 0.012121\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 1261/1261 [09:28<00:00,  2.59it/s, accuracy=0.182, cost=3.94]\n",
      "test minibatch loop: 100%|██████████| 4/4 [00:00<00:00,  7.73it/s, accuracy=0, cost=4.81]   \n",
      "train minibatch loop:   0%|          | 0/1261 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 1, pass acc: 0.012121, current acc: 0.024242\n",
      "time taken: 569.0105755329132\n",
      "epoch: 1, training loss: 3.600702, training acc: 0.192252, valid loss: 5.924614, valid acc: 0.024242\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 1261/1261 [09:28<00:00,  2.60it/s, accuracy=0.227, cost=3.65]\n",
      "test minibatch loop: 100%|██████████| 4/4 [00:00<00:00,  7.78it/s, accuracy=0, cost=4.77]   \n",
      "train minibatch loop:   0%|          | 0/1261 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "time taken: 569.0057277679443\n",
      "epoch: 2, training loss: 3.432430, training acc: 0.222849, valid loss: 5.912193, valid acc: 0.024242\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 1261/1261 [09:28<00:00,  2.59it/s, accuracy=0.227, cost=3.45]\n",
      "test minibatch loop: 100%|██████████| 4/4 [00:00<00:00,  7.75it/s, accuracy=0, cost=4.91]  \n",
      "train minibatch loop:   0%|          | 0/1261 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "time taken: 568.930771112442\n",
      "epoch: 3, training loss: 3.292074, training acc: 0.248348, valid loss: 6.023255, valid acc: 0.012121\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 1261/1261 [09:28<00:00,  2.59it/s, accuracy=0.318, cost=3.29]\n",
      "test minibatch loop: 100%|██████████| 4/4 [00:00<00:00,  7.74it/s, accuracy=0, cost=4.86]   "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "time taken: 568.9259750843048\n",
      "epoch: 4, training loss: 3.198256, training acc: 0.267873, valid loss: 5.987054, valid acc: 0.012121\n",
      "\n",
      "break epoch:5\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "EARLY_STOPPING, CURRENT_CHECKPOINT, CURRENT_ACC, EPOCH = 3, 0, 0, 0\n",
    "\n",
    "while True:\n",
    "    lasttime = time.time()\n",
    "    if CURRENT_CHECKPOINT == EARLY_STOPPING:\n",
    "        print('break epoch:%d\\n' % (EPOCH))\n",
    "        break\n",
    "\n",
    "    train_acc, train_loss, test_acc, test_loss = 0, 0, 0, 0\n",
    "    pbar = tqdm(\n",
    "        range(0, len(train_X), batch_size), desc = 'train minibatch loop'\n",
    "    )\n",
    "    for i in pbar:\n",
    "        index = min(i + batch_size, len(train_X))\n",
    "        batch_x = train_X[i: index]\n",
    "        batch_y = train_Y[i: index]\n",
    "        acc, cost, _ = sess.run(\n",
    "            [model.accuracy, model.cost, model.optimizer],\n",
    "            feed_dict = {\n",
    "                model.Y: batch_y,\n",
    "                model.X: batch_x,\n",
    "            },\n",
    "        )\n",
    "        assert not np.isnan(cost)\n",
    "        train_loss += cost\n",
    "        train_acc += acc\n",
    "        pbar.set_postfix(cost = cost, accuracy = acc)\n",
    "        \n",
    "    pbar = tqdm(range(0, len(test_X), batch_size), desc = 'test minibatch loop')\n",
    "    for i in pbar:\n",
    "        index = min(i + batch_size, len(test_X))\n",
    "        batch_x = test_X[i: index]\n",
    "        batch_y = test_Y[i: index]\n",
    "        acc, cost = sess.run(\n",
    "            [model.accuracy, model.cost],\n",
    "            feed_dict = {\n",
    "                model.Y: batch_y,\n",
    "                model.X: batch_x,\n",
    "            },\n",
    "        )\n",
    "        test_loss += cost\n",
    "        test_acc += acc\n",
    "        pbar.set_postfix(cost = cost, accuracy = acc)\n",
    "\n",
    "    train_loss /= len(train_X) / batch_size\n",
    "    train_acc /= len(train_X) / batch_size\n",
    "    test_loss /= len(test_X) / batch_size\n",
    "    test_acc /= len(test_X) / batch_size\n",
    "\n",
    "    if test_acc > CURRENT_ACC:\n",
    "        print(\n",
    "            'epoch: %d, pass acc: %f, current acc: %f'\n",
    "            % (EPOCH, CURRENT_ACC, test_acc)\n",
    "        )\n",
    "        CURRENT_ACC = test_acc\n",
    "        CURRENT_CHECKPOINT = 0\n",
    "    else:\n",
    "        CURRENT_CHECKPOINT += 1\n",
    "        \n",
    "    print('time taken:', time.time() - lasttime)\n",
    "    print(\n",
    "        'epoch: %d, training loss: %f, training acc: %f, valid loss: %f, valid acc: %f\\n'\n",
    "        % (EPOCH, train_loss, train_acc, test_loss, test_acc)\n",
    "    )\n",
    "    EPOCH += 1"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
